#include "cc.h"

/**
 MSG SYNTAX:

	MSG:
		[ REQUEST_HEAD | RESPONSE_HEAD ] PARAMETER_LINE \n

	REQUEST_HEAD:
		request : REQUEST_METHOD \n

	RESPONSE_HEAD:
		response : RESPONSE_STATUS \n

	RESPONSE_STATUS:
		[ ok | error ]

	PARAMETER_LINE:
		parameter_name : parameter_value \n
 
**/

typedef struct {
	char msg_buf[MSG_LEN];
	int msg_len;
	int msg_off;
	int msg_next;
} msg_t;

typedef struct {
	char *cmd;
	int (*cb) (msg_t *r, msg_t *w);
} msgfunc_t;


static int g_msg_debug = 1;

/*********************************************************
 *
 * BASIC
 *
 ********************************************************/

static INLINE msg_t *msg_new(msg_t *msg)
{
	memset(msg->msg_buf, 0, sizeof(msg->msg_buf));
	msg->msg_len = MSG_LEN;
	msg->msg_off = 0;
	msg->msg_next = 0;
	return msg;
}

static INLINE int msg_read(msg_t *msg, int fd)
{
	char *p;
	int ret;
	int i;

	ret = ipc_for_read(fd, 1, 0);
	if(ret <= 0)
		return ret;

	if(msg->msg_next) {
		for(i = 0 ; i < msg->msg_off - msg->msg_next ; i ++)
			msg->msg_buf[i] = msg->msg_buf[i + msg->msg_next];
		memset(msg->msg_buf + i, 0, msg->msg_len - (msg->msg_off - msg->msg_next));
		msg->msg_off = msg->msg_off - msg->msg_next;
		msg->msg_next = 0;

//		if(g_msg_debug)
//			log_debug("MSG: read, adjust off to %d\n", msg->msg_off);
	}

	ret = read(fd, msg->msg_buf + msg->msg_off, msg->msg_len - msg->msg_off - 1);
	if(ret <= 0)
		return -1;

	if(msg->msg_off == 0) {
		char *check_tag = "request : ";
		int check_len = 10;

		if(check_len > ret)
			check_len = ret;

		if(strncmp(msg->msg_buf, check_tag, check_len))
			return -1;
	}

	msg->msg_off += ret;

	p = strstr(msg->msg_buf, "\n\n");
	if(p) {
		if(g_msg_debug)
			log_info("MSG: recv {\n%s}\n", msg->msg_buf);

		p[1] = '\0';
		msg->msg_next = p - msg->msg_buf + 2;
		
		return 1;
	}

	return 0;
}

static INLINE int msg_write(msg_t *msg, int fd)
{
	time_t t_start;
	time_t t_now;
	int ret;
	int i;

	t_start = time(NULL);
	
	for(i = 0 ; i < msg->msg_off ; ) {
		t_now = time(NULL);
		if(t_now - t_start > 2)
			return -1;

		ret = ipc_for_write(fd, 1, 0);
		if(ret < 0)
			return -1;
		else if(ret == 0)
			continue;

		ret = write(fd, msg->msg_buf + i, msg->msg_off - i);
		if(ret <= 0)
			return -1;

		i += ret;
	}

	if(g_msg_debug)
		log_info("MSG: send {\n%s}\n", msg->msg_buf);

	return 1;
}

static INLINE int msg_find(msg_t *msg, char *key, char *value, int value_len)
{
	char tag[MSG_NLEN + 5];
	int len;
	char *p;
	char *q;

	value[0] = '\0';

	len = snprintf(tag, sizeof(tag) - 1, "%s : ", key);
	
	p = strstr(msg->msg_buf, tag);
	if(p == NULL)
		return -1;

	p += len;
	q = strchr(p, '\n');
	if(q == NULL)
		return -1;

	*q = '\0';
	str_ncopy(value, p, value_len);
	*q = '\n';
	
	return 0;
}

#define msg_get_req(msg, method)			msg_find((msg), "request", (method), sizeof(method))
#define msg_get_param(msg, name, value)			msg_find((msg), (name), (value), sizeof(value))

static INLINE int msg_set_res(msg_t *msg, int res)
{
	msg->msg_off = sprintf(msg->msg_buf, "response : %s\n\n", res ? "error" : "ok");
	msg->msg_buf[msg->msg_off] = '\0';
	return 0;
}

static INLINE int msg_set_param(msg_t *msg, char *name, char *value)
{
	int nlen, vlen;
	int ret;

	nlen = strlen(name);
	vlen = strlen(value);

	if(nlen >= MSG_NLEN || vlen >= MSG_VLEN || msg->msg_off + nlen + vlen + 5 >= msg->msg_len)
		return -1;

	if(msg->msg_off > 0)
		msg->msg_off --;

	ret = sprintf(msg->msg_buf + msg->msg_off, "%s : %s\n\n", name, value);
	
	msg->msg_off += ret;

	msg->msg_buf[msg->msg_off] = '\0';

	return 0;
}

static INLINE int msg_set_iparam(msg_t *msg, char *name, int value)
{
	char svalue[32];

	sprintf(svalue, "%d", value);
	return msg_set_param(msg, name, svalue);
}

static INLINE int msg_len(msg_t *msg)
{
	return msg->msg_off;
}

static INLINE void msg_show(msg_t *msg)
{
	log_info("MSG: \n{\n%s}\n", msg->msg_buf);
}

/*********************************************************
 *
 * MSG
 *
 ********************************************************/

static int msg_reload(msg_t *r, msg_t *w)
{
	msg_set_res(w, MSG_ERROR);
	return 0;
}

static int msg_debug(msg_t *r, msg_t *w)
{
	char level[32];

	msg_get_param(r, "level", level);

	if(! strcmp(level, "msgon"))
		g_msg_debug = 1;
	else if(! strcmp(level, "msgoff"))
		g_msg_debug = 0;
	else if(! strcmp(level, "emerg"))
		log_level(LOG_EMERG);
	else if(! strcmp(level, "alert"))
		log_level(LOG_ALERT);
	else if(! strcmp(level, "crit"))
		log_level(LOG_CRIT);
	else if(! strcmp(level, "err"))
		log_level(LOG_EMERG);
	else if(! strcmp(level, "warning"))
		log_level(LOG_WARNING);
	else if(! strcmp(level, "notice"))
		log_level(LOG_NOTICE);
	else if(! strcmp(level, "info"))
		log_level(LOG_INFO);
	else if(! strcmp(level, "debug"))
		log_level(LOG_DEBUG);

	msg_set_res(w, MSG_OK);
	return 0;
}

static int msg_quit(msg_t *r, msg_t *w)
{
	thr_quit();
	
	msg_set_res(w, MSG_OK);
	return 0;
}

static int msg_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_load_all(0);
	if(ret == 0)
		ast_cmd("module reload");
	msg_set_res(w, ret);
	return 0;
}

static int msg_user_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_user_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_user_group_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_user_group_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_user_relation_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_user_relation_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_extension_setting_load_all(msg_t *r, msg_t *w)
{
	int ret = 0;

	ret += db_extension_setting_load_all(1);
	ret += db_extension_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_extension_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_extension_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_trunk_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_trunk_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_trunk_setting_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_trunk_setting_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_setting_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_setting_load_all(1);
	msg_set_res(w, ret);
	return 0;
}

static int msg_fax_setting_load_all(msg_t *r, msg_t *w)
{
	int ret;

	ret = db_fax_setting_load_all(0);
	ret += db_extension_load_all(0);
	ret += db_trunk_load_all(1);

	ret = ret ? -1 : 0;
	msg_set_res(w, ret);
	return 0;
}

static int msg_regcode_load(msg_t *r, msg_t *w)
{
	int ret;

	ret = regcode_load();
	msg_set_res(w, ret);
	return 0;
}

static int msg_regcode_create(msg_t *r, msg_t *w)
{
	int ret;

	ret = regcode_create();
	msg_set_res(w, ret);
	return 0;
}

static int msg_channel_type(msg_t *r, msg_t *w)
{
	char channel_type[32];
	char unique_id[32];
	int ret;

	msg_get_param(r, "unique_id", unique_id);

	ret = channel_get_ch(unique_id, NULL, NULL, NULL, NULL, channel_type);
	msg_set_res(w, ret);
	if(ret)
		msg_set_param(w, "errmsg", ERRMSG_NOFOUND_CHANNEL);
	else
		msg_set_param(w, "channel_type", channel_type);

	return 0;
}

static int msg_channel_get_callerid(msg_t *r, msg_t *w)
{
	char errmsg[MSG_VLEN];
	char caller_id[32];
	char unique_id[32];
	int ret;

	msg_get_param(r, "unique_id", unique_id);

	ret = channel_get_caller_id(unique_id, caller_id, errmsg);
	msg_set_res(w, ret);
	if(ret)
		msg_set_param(w, "errmsg", errmsg);
	else
		msg_set_param(w, "caller_id", caller_id);

	return 0;
}

static int msg_channel_set_call_id(msg_t *r, msg_t *w)
{
	char errmsg[MSG_VLEN];
	char unique_id[32];
	char caller_id[32];
	char called_id[32];
	int ret;

	msg_get_param(r, "unique_id", unique_id);
	msg_get_param(r, "caller_id", caller_id);
	msg_get_param(r, "called_id", called_id);

	ret = channel_set_call_id(unique_id, caller_id, called_id, errmsg);
	msg_set_res(w, ret);
	if(ret)
		msg_set_param(w, "errmsg", errmsg);
	else
		msg_set_param(w, "caller_id", caller_id);

	return 0;
}

static int msg_channel_dump(msg_t *r, msg_t *w)
{
	char prefix[32];
	int ret;

	msg_get_param(r, "prefix", prefix);

	ret = channel_dump(prefix);
	msg_set_res(w, ret);

	return 0;
}

static int msg_chan_dump(msg_t *r, msg_t *w)
{
	char prefix[32];
	int ret;

	msg_get_param(r, "prefix", prefix);

	ret = chan_dump(prefix);
	msg_set_res(w, ret);

	return 0;
}

static int msg_fax_connect(msg_t *r, msg_t *w)
{
	char errmsg[MSG_VLEN];
	char direction[32];
	char unique_id[32];
	char caller_id[32];
	char called_id[32];
	char lsid[128];
	char lhinfo[128];
	char fax_id[32];
	int ret;

	msg_get_param(r, "direction", direction);
	msg_get_param(r, "fax_id", fax_id);
	msg_get_param(r, "unique_id", unique_id);
	msg_get_param(r, "caller_id", caller_id);
	msg_get_param(r, "called_id", called_id);

	ret = fax_connect(direction, fax_id, unique_id, caller_id, called_id, lsid, lhinfo, errmsg);

	msg_set_res(w, ret);
	if(ret)
		msg_set_param(w, "errmsg", errmsg);
	else {
		msg_set_param(w, "lsid", lsid);
		msg_set_param(w, "lhinfo", lhinfo);
	}

	return 0;
}

static int msg_fax_cancel(msg_t *r, msg_t *w)
{
	char errmsg[MSG_VLEN];
	char fax_id[32];
	int ret;

	msg_get_param(r, "fax_id", fax_id);

	ret = fax_cancel(fax_id, errmsg);

	msg_set_res(w, ret);
	if(ret)
		msg_set_param(w, "errmsg", errmsg);

	return 0;
}

static int msg_fax_debug(msg_t *r, msg_t *w)
{
	char debug[32];
	int on = 0;

	msg_get_param(r, "debug", debug);

	if(! strcmp(debug, "yes") || ! strcmp(debug, "on") || ! strcmp(debug, "true") || ! strcmp(debug, "1"))
		on = 1;

	fax_debug(on);

	msg_set_res(w, 0);

	return 0;
}

static int msg_in_inbound_blacklist(msg_t *r, msg_t *w)
{
	char unique_id[32];
	char number[32];
	channel_t oc;
	int in = 0;
	int ret;

	msg_get_param(r, "unique_id", unique_id);
	msg_get_param(r, "number", number);

	ret = channel_get(unique_id, &oc);
	if(ret == 0) {
		if(oc.ch.itype == TYPE_IS_TRUNK) {
			if(number[0])
				in = db_is_in_inbound_blacklist(number);
		}
	}

	msg_set_res(w, 0);
	msg_set_param(w, "in", in ? "yes" : "no");

	return 0;
}

static int msg_set_time(msg_t *r, msg_t *w)
{
	char ntp_update[32];
	char tz[128];
	int ret = -1;
	char t[32];

	msg_get_param(r, "timezone", tz);
	msg_get_param(r, "time", t);
	msg_get_param(r, "ntp_update", ntp_update);

	if(t[0])
		ret = db_time_update(t);

	if(tz[0])
		ret = db_timezone_update(tz);

	if(! strcmp(ntp_update, "yes"))
		ret = db_ntp_update();

	msg_set_res(w, ret);
	if(ret)
		msg_set_param(w, "errmsg", ERRMSG_EXEC);

	return 0;
}

static int msg_regcode_dump(msg_t *r, msg_t *w)
{
	regcode_dump();
	msg_set_res(w, 0);

	return 0;
}

static int msg_help(msg_t *r, msg_t *w);

static msgfunc_t g_msgfunc[] = {
	{	"reload", 				&msg_reload				},
	{	"debug", 				&msg_debug				},
	{	"help",					&msg_help				},
	{	"quit", 				&msg_quit				},

	{	"load_all", 				&msg_load_all				},
	{	"user_load_all", 			&msg_user_load_all			},
	{	"user_group_load_all", 			&msg_user_group_load_all		},
	{	"user_relation_load_all", 		&msg_user_relation_load_all		},
	{	"extension_setting_load_all",		&msg_extension_setting_load_all		},
	{	"extension_load_all", 			&msg_extension_load_all			},
	{	"trunk_load_all",	 		&msg_trunk_load_all			},
	{	"trunk_setting_load_all",		&msg_trunk_setting_load_all		},
	{	"setting_load_all", 			&msg_setting_load_all			},
	{	"fax_setting_load_all", 		&msg_fax_setting_load_all		},

	{	"regcode_load",				&msg_regcode_load			},
	{	"regcode_create",			&msg_regcode_create			},

	{	"channel_type",				&msg_channel_type			},
	{	"channel_get_callerid",			&msg_channel_get_callerid		},
	{	"channel_set_call_id",			&msg_channel_set_call_id		},
	{	"channel_dump",				&msg_channel_dump			},

	{	"chan_dump",				&msg_chan_dump				},

	{	"fax_connect",				&msg_fax_connect			},
	{	"fax_cancel",				&msg_fax_cancel				},
	{	"fax_debug",				&msg_fax_debug				},

	{	"in_inbound_blacklist",			&msg_in_inbound_blacklist		},

	{	"set_time",				&msg_set_time				},

	{	"regcode_dump",				&msg_regcode_dump			},

	{	NULL,					NULL					}
};

static int msg_help(msg_t *r, msg_t *w)
{
	char cmd[32];
	int i;

	msg_set_res(w, 0);

	for(i = 0 ; g_msgfunc[i].cmd ; i ++) {
		sprintf(cmd, "%d", i);
		msg_set_param(w, cmd, g_msgfunc[i].cmd);
	}

	return 0;
}

static int msg_exec(msg_t *r, msg_t *w)
{
	char name[MSG_NLEN];
	int i;

	msg_get_req(r, name);

	if(name[0])
	for(i = 0 ; g_msgfunc[i].cmd ; i ++) {
		if(! strcmp(g_msgfunc[i].cmd, name)) {
			(*g_msgfunc[i].cb) (r, w);
			return msg_len(w);
		}
	}

	msg_set_res(w, MSG_ERROR);
	msg_set_param(w, "errmsg", ERRMSG_UNIMPLEMENT);

	return msg_len(w);
}

static void *msg_proc(void *arg)
{
	int newfd = (int) arg;
	msg_t rmsg, wmsg;
	msg_t *r, *w;
	int ret;

	if(g_msg_debug)
		log_info("MSG: %d connected\n", newfd);

	r = msg_new(&rmsg);

	while(1) {
		if(thr_is_quitted())
			break;

		ret = msg_read(r, newfd);
		if(ret < 0)
			break;
		else if(ret == 0)
			continue;

		w = msg_new(&wmsg);

		ret = msg_exec(r, w);
		if(ret <= 0) {
			log_alert("MSG: failed for no response\n");
			break;
		}

		ret = msg_write(w, newfd);
		if(ret <= 0)
			break;
	}

	close(newfd);
	
	if(g_msg_debug)
		log_info("MSG: %d disconnected\n", newfd);

	return NULL;
}

static void *msg_server(void *arg)
{
	int fd = (int) arg;
	int newfd;
	SUN addr;
	int ret;

	log_info("MSG: started\n");
	log_debug("MSG: main fd = %d\n", fd);

	while(1) {
		if(thr_is_quitted())
			break;

		ret = ipc_for_read(fd, 3, 0);
		if(ret <= 0) {
			continue;
		}

		newfd = ipc_accept(fd, &addr);
		if(newfd < 0)
			continue;

		ret = thr_new(msg_proc, (void *) newfd);
		if(ret) {
			log_alert("MSG: new proc() failed\n");
			close(newfd);
			continue;
		}
	}

	log_info("MSG: stopped\n");

	close(fd);

	return NULL;
}

int msg_init()
{
	int ret;
	int fd;

	umask(0);

	fd = ipc_server(MSG_NAME, 0777);
	if(fd < 0) {
		log_emerg("MSG: bind %s failed, ret = %d\n", MSG_NAME, ret);
		return -1;
	}

	ret = thr_new(&msg_server, (void *)fd);
	if(ret) {
		log_emerg("MSG: new server() failed\n");
		return -2;
	}

	return 0;
}
